﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Text.RegularExpressions;

namespace IdeaNMR.Shared
{
    public class SpinachSetup
    {
        //public static string TranslateToSpinach(StringReader pulseProgram, StringReader setupMacro)
        public static string TranslateToSpinach(StringReader pulseProgram, string experimentScipt)
        {
            // Read input from bruker pulse program
            string brukerInputString = pulseProgram.ReadToEnd();

            // Read input from setup macro
            //string macroInputString = setupMacro.ReadToEnd();
            string macroInputString = experimentScipt;

            // ADD OUTPUT FILE PARAMETER

            // Remove both bruker comments and blank lines from the pulse program
            // (each occurrence is replaced with an empty string)
            // Regex for bruker comment: semi-colon(;),anything(.*),newline(\n)
            // Regex for blank line: startline(^),spaces(\s*),endline($),newline(\n)
            // Regex used in multiline mode, to allow start and end of line detection
            brukerInputString = Regex.Replace(brukerInputString, @"(;.*\n|^\s*$\n)", "", RegexOptions.Multiline);

            //Console.Write(brukerInputString);

            // Extract #define statements and save as an array of key, value pairs
            Dictionary<string, string> defineStatements = new Dictionary<string, string>();

            // Regex is: #define,spaces,key,spaces,value,return
            // string.gfind(accu, "#define%s*([%w_]+)%s*([%*%s%w:]+)\n")
            //Regex exp = new Regex(@"\#define\s*([\w_]+)\s*([\*\s\w:]+)\r\n");
            // ALISTAIR modified 13/09/11: Remove \r from definition for end of line 
            Regex exp = new Regex(@"\#define\s*([\w_]+)\s*([\*\s\w:]+)\n");
            MatchCollection MatchList = exp.Matches(brukerInputString);

            foreach (Match aMatch in MatchList)
            {
                // Get the number of groups (excluding the first group, which is the full match)
                //Console.WriteLine("No. of groups in current match: " + (aMatch.Groups.Count-1));

                defineStatements.Add(aMatch.Groups[1].Value, aMatch.Groups[2].Value);

            }

            /*DIAGNOSTICS
            foreach (KeyValuePair<string, string> kv in defineStatements)
            {
                Console.WriteLine("#define key:_" + kv.Key + "_,value:_" + kv.Value + "_");
            }
            */


            // Extract "calculus" statements and save as an array of key, value pairs
            // By "calculus", we mean any calculations perfomed
            // within double quotes in the bruker pulse program
            Dictionary<string, string> calculusStatements = new Dictionary<string, string>();

            // Regex: double quote("),spaces,key,spaces,equals(=),spaces,value,double quote(")
            // double quote (") inside C# 'verbatim string' is represented by writing it twice ("")
            Regex calculusRegEx = new Regex(@"""\s*([\w_]+)\s*=\s*([\.\-\+\*\s\w:/\(\)]+)""");
            MatchCollection calculusMatchList = calculusRegEx.Matches(brukerInputString);
            //Console.WriteLine("Count from MatchList: " + calculusMatchList.Count);

            foreach (Match aMatch in calculusMatchList)
            {
                // Get the number of groups (excluding the first group, which is the full match)
                //Console.WriteLine("No. of groups in current match: " + (aMatch.Groups.Count - 1));

                string aValue = aMatch.Groups[2].Value;

                // resolve explicit delays in calculus
                if (Regex.IsMatch(aValue, @"(\d+)u"))
                {
                    // for microsecond delays (u) extract digits and multiply by 1e-6
                    //Console.WriteLine("ORIGINAL STRING: " + aMatch.Groups[2].Value);
                    aValue = Regex.Replace(aValue, @"(\d+)u", @"$1*1e-6");
                    //Console.WriteLine("REPLACED STRING: " + aValue);
                }

                if (Regex.IsMatch(aValue, @"(\d+)m"))
                {
                    // for millisecond delays (m) extract digits and multiply by 1e-3
                    //Console.WriteLine("ORIGINAL STRING: " + aMatch.Groups[2].Value);
                    aValue = Regex.Replace(aValue, @"(\d+)m", @"$1*1e-3");
                    //Console.WriteLine("REPLACED STRING: " + aValue);
                }

                if (Regex.IsMatch(aValue, @"p(\d+)"))
                {
                    // for each pulse (p), prefix "v." in front of pulse
                    //Console.WriteLine("ORIGINAL STRING: " + aMatch.Groups[2].Value);
                    aValue = Regex.Replace(aValue, @"p(\d+)", @"v.p$1");
                    //Console.WriteLine("REPLACED STRING: " + aValue);
                }

                if (Regex.IsMatch(aValue, @"d(\d+)"))
                {
                    // for each delay (d), prefix "v." in front of delay
                    //Console.WriteLine("ORIGINAL STRING: " + aMatch.Groups[2].Value);
                    aValue = Regex.Replace(aValue, @"d(\d+)", @"v.d$1");
                    //Console.WriteLine("REPLACED STRING: " + aValue);
                }

                if (Regex.IsMatch(aValue, @"in(\d+)"))
                {
                    // for each increment (in), prefix "v." in front of pulse
                    //Console.WriteLine("ORIGINAL STRING: " + aMatch.Groups[2].Value);
                    aValue = Regex.Replace(aValue, @"in(\d+)", @"v.in$1");
                    //Console.WriteLine("REPLACED STRING: " + aValue);
                }

                if (Regex.IsMatch(aValue, @"cnst(\d+)"))
                {
                    // for each constant (cnst), prefix "v." in front of constant
                    //Console.WriteLine("ORIGINAL STRING: " + aMatch.Groups[2].Value);
                    aValue = Regex.Replace(aValue, @"cnst(\d+)", @"v.cnst$1");
                    //Console.WriteLine("REPLACED STRING: " + aValue);
                }

                // save each key, value pair to the calculus array
                calculusStatements.Add(aMatch.Groups[1].Value, aValue);

            }

            /*DIAGNOSTIC
            foreach (KeyValuePair<string, string> kv in calculusStatements)
            {
                //Console.WriteLine("calculus key:_" + kv.Key + "_,value:_" + Regex.Escape(kv.Value) + "_");
                //Console.WriteLine("calculus key:_" + kv.Key + "_,value:_" + Regex.Unescape(kv.Value) + "_");
            }
            */

            // Find any "if" statements with bruker pulse program
            // If found, extract the "if" block statements
            // and the "else" block statements (if any)

            // define an array to hold the argument, value pairs of each "if" condition
            Dictionary<string, string[]> ifStatements = new Dictionary<string, string[]>();

            // Regex for "if" condition:
            //    "if",spaces,double quote(""),argument([\w_]+),anything(.*),double equals(==),spaces,value([\w_]+),
            //    spaces,double quote(""),spaces,open curly bracket({),spaces,if statement block(.*),spaces,close curly bracket(}),
            //    spaces,"else",spaces,open curly bracket({),spaces,else statement block(.*),spaces,close curly bracket(})
            // i.e. of form: if "argument == value" { if block } else { else block }
            Regex ifStatementRegEx = new Regex(@"if\s+""\s*([\w_]+).*==\s*([\w_]+)\s*""\s*{\s*(.*)\s*}\s*else\s*{\s*(.*)\s*}\s*");
            MatchCollection ifStatementMatchList = ifStatementRegEx.Matches(brukerInputString);
            //Console.WriteLine("Count from ifStatememtMatchList: " + ifStatementMatchList.Count);

            foreach (Match aMatch in ifStatementMatchList)
            {
                // Get the number of groups (excluding the first group, which is the full match)
                //Console.WriteLine("No. of groups in current match: " + (aMatch.Groups.Count - 1));
                //Console.WriteLine("Full Match: " + aMatch.Groups[0].Value);

                string[] ifValueIfBlockElseBlock = new string[3];
                ifValueIfBlockElseBlock[0] = aMatch.Groups[2].Value;
                ifValueIfBlockElseBlock[1] = aMatch.Groups[3].Value;
                ifValueIfBlockElseBlock[2] = aMatch.Groups[4].Value;

                ifStatements.Add(aMatch.Groups[1].Value, ifValueIfBlockElseBlock);

                /*DIAGNOSTIC
                for (int i = 0; i < aMatch.Groups.Count; i++)
                {
                    // can choose between escaped or unescaped string
                    //Console.WriteLine("Group Num " + i + ":__" + Regex.Escape(aMatch.Groups[i].Value) + "__");
                    //Console.WriteLine("Group Num " + i + ":__" + Regex.Unescape(aMatch.Groups[i].Value) + "__");
                }
                */

            }

            foreach (KeyValuePair<string, string[]> if_Kv in ifStatements)
            {
                //Console.WriteLine("if argument: " + if_Kv.Key);
                //Console.WriteLine("if value: " + if_Kv.Value[0]);
                //Console.WriteLine("if statements: " + if_Kv.Value[1]);
                //Console.WriteLine("else statements: " + if_Kv.Value[2]);

                // flag to indicate if the argument of the "if statement"
                // was found in the bruker pulse program
                bool ifStatementArgFound = false;

                foreach (KeyValuePair<string, string> calc_Kv in calculusStatements)
                {

                    //Console.WriteLine("calc key: " + calc_Kv.Key);
                    //Console.WriteLine("calc value: " + calc_Kv.Value);

                    if (if_Kv.Key.Equals(calc_Kv.Key))
                    {
                        // argument has been found in pulse program
                        ifStatementArgFound = true;

                        //Console.WriteLine("KEY MATCH FOUND");
                        //Console.WriteLine("if argument: " + if_Kv.Key);
                        //Console.WriteLine("calc key: " + calc_Kv.Key);

                        if (if_Kv.Value[0].Equals(calc_Kv.Value))
                        {
                            //Console.WriteLine("VALUE MATCH FOUND");
                            //Console.WriteLine("if value: " + if_Kv.Value[0]);
                            //Console.WriteLine("calc value: " + calc_Kv.Value);

                            // Calculus value matches if condition.
                            // Replace entire "if" statement with the "if block" only
                            brukerInputString = Regex.Replace(brukerInputString, @"if\s+""\s*([\w_]+).*==\s*([\w_]+)\s*""\s*{\s*(.*)\s*}\s*else\s*{\s*(.*)\s*}\s*", if_Kv.Value[1]);
                        }
                        else
                        {
                            // Calculus value does not match if condition.
                            // Replace entire "if" statement with the "else block" only
                            brukerInputString = Regex.Replace(brukerInputString, @"if\s+""\s*([\w_]+).*==\s*([\w_]+)\s*""\s*{\s*(.*)\s*}\s*else\s*{\s*(.*)\s*}\s*", if_Kv.Value[2]);
                        } // end if vaules equal
                    } // end if keys equal

                } // end for each "calculus" statement

                // if the argument of the "if" statement was not found
                // in the pulse program, then use the "else block"
                // (not sure how bruker pulse program deal with this senario??)
                if (!ifStatementArgFound)
                {
                    // Replace entire "if" statement with the "else block" only
                    brukerInputString = Regex.Replace(brukerInputString, @"if\s+""\s*([\w_]+).*==\s*([\w_]+)\s*""\s*{\s*(.*)\s*}\s*else\s*{\s*(.*)\s*}\s*", if_Kv.Value[2]);
                }

            }  // end for each "if" statement

            //Console.Write(Regex.Escape(brukerInputString));

            // Remove any lines that contain any of the text strings specified.
            // Regex: startline(^),anything(.*),alternative string literals,anything(.*),endline($),newline(\n)
            // Regex used in multiline mode, to allow start and end of line detection
            // Double quote (") inside C# 'verbatim string' is represented by writing it twice ("")
            brukerInputString = Regex.Replace(brukerInputString, @"^.*(exit|#include|prosol|aqseq| ze|#define|"").*$\n", "", RegexOptions.Multiline);

            // create a new "phase+calculus" array
            // to add both the phase parameters and the "calculus" parameters
            Dictionary<string, string> phaseCalculusStatements = new Dictionary<string, string>();

            // add line "%% Phase Table" to start of array
            // NOTE: "PhaseTableTitle" IS ARBITRARY KEY STRING
            //phaseCalculusStatements.Add("PhaseTableTitle", @"%% Phase Table");

            // find the phase parameters (ph) and add value to phase+calculus array
            //Regex phaseRegEx = new Regex(@"(ph\d+)\s*=\s*(.*)\r\n");
            // ALISTAIR modified 13/09/11: Remove \r from definition for end of line 
            Regex phaseRegEx = new Regex(@"(ph\d+)\s*=\s*(.*)\n");
            MatchCollection phaseMatchList = phaseRegEx.Matches(brukerInputString);
            //Console.WriteLine("Count from MatchList: " + phaseMatchList.Count);

            foreach (Match aMatch in phaseMatchList)
            {
                // Get the number of groups (excluding the first group, which is the full match)
                //Console.WriteLine("No. of groups in current match: " + (aMatch.Groups.Count - 1));

                string phaseValue = aMatch.Groups[2].Value;

                // If phase shift multiplier is specified in with (), then retain this multiplier
                // Regex is: open bracket,multiplier(digits),close bracket,spaces,value
                // Remove brackets, add prefix "*[" and suffix "]" to phase value
                if (Regex.IsMatch(phaseValue, @"\(\d+\)"))
                {
                    //Console.WriteLine("ORIGINAL STRING with multipler: " + aMatch.Groups[2].Value);
                    phaseValue = Regex.Replace(phaseValue, @"\((\d+)\)\s*(.*)", @"$1*[$2]");
                    //Console.WriteLine("REPLACED STRING: " + phaseValue);
                }
                else
                {
                    // If no multiplier is given for phases,
                    // then use default 90 degrees phase shift multiplier
                    // Add prefix "90*[" and suffix "]" to phase value
                    //Console.WriteLine("ORIGINAL STRING without multiplier: " + aMatch.Groups[2].Value);
                    phaseValue = "90*[" + phaseValue + "]";
                    //Console.WriteLine("REPLACED STRING: " + phaseValue);
                }

                /*DIAGNOSTIC
                for (int i = 0; i < aMatch.Groups.Count; i++)
                {
                    // can choose between escaped or unescaped string
                    //Console.WriteLine("Group Num " + i + ":__" + Regex.Escape(aMatch.Groups[i].Value) + "__");
                    //Console.WriteLine("Group Num " + i + ":__" + Regex.Unescape(aMatch.Groups[i].Value) + "__");
                }
                */

                // save each key, value pair to the phase+calculus array
                phaseCalculusStatements.Add(aMatch.Groups[1].Value, phaseValue);

            }

            // Append each of the key, value pairs
            // from the "calculus" array to the "phase+calculus" array
            foreach (string calcKey in calculusStatements.Keys)
            {
                phaseCalculusStatements.Add(calcKey, calculusStatements[calcKey]);
            }

            /*DIAGNOSTIC
            foreach (KeyValuePair<string, string> kv in phaseCalculusStatements)
            {
                //Console.WriteLine("phase+calculus key:_" + kv.Key + "_,value:_" + kv.Value + "_");
            }
            */

            // Declare list for all "define delay" statements
            List<string> defineDelays = new List<string>();

            //Regex defDelayRegEx = new Regex(@"define delay\s*(.*)\r\n");
            // ALISTAIR modified 13/09/11: Remove \r from definition for end of line 
            Regex defDelayRegEx = new Regex(@"define delay\s*(.*)\n");
            MatchCollection defDelayMatchList = defDelayRegEx.Matches(brukerInputString);
            //Console.WriteLine("Count from MatchList: " + defDelayMatchList.Count);

            foreach (Match aMatch in defDelayMatchList)
            {
                // Get the number of groups (excluding the first group, which is the full match)
                //Console.WriteLine("No. of groups in current match: " + (aMatch.Groups.Count - 1));

                // save each key, value pair to the phase+calculus array
                defineDelays.Add(aMatch.Groups[1].Value);
            }

            // Extra delays not in pulse program, but hard coded for Spinach input
            defineDelays.Add("DELTA1");
            defineDelays.Add("DELTA2");
            defineDelays.Add("DELTA3");
            defineDelays.Add("DELTA4");

            /*DIAGNOSTIC
            foreach (string defDelay in defineDelays)
            {
                Console.WriteLine("Define delay:_" + defDelay + "_");
            }
            */

            // START with CONVERSION TO MATLAB FUNCTIONS

            // Save the current input format to a separate string
            string spinachInputString = brukerInputString;

            // Set power calibartion for all 4 channels
            // Get values from spectrometer specified by user

            /* TODO: GET SPECTROMETER VALUES
            p90={}; pl90={}
            p90[1]=spectrometer[now].calibration_pulse90_f1; pl90[1]=spectrometer[now].calibration_power_db_f1;
            p90[2]=spectrometer[now].calibration_pulse90_f2; pl90[2]=spectrometer[now].calibration_power_db_f2;
            p90[3]=spectrometer[now].calibration_pulse90_f3; pl90[3]=spectrometer[now].calibration_power_db_f3;
            p90[4]=spectrometer[now].calibration_pulse90_f4; pl90[4]=spectrometer[now].calibration_power_db_f4;
            */
            // TEMP, HARDCORE VALUES FOR p90 AND pl90
            double[] p90 = new double[4];
            double[] pl90 = new double[4];
            p90[1] = 9; pl90[1] = 5;
            p90[2] = 17.5; pl90[2] = 0.5;
            p90[3] = 38.2; pl90[3] = -2.9;


            // For each #define statement,
            // replace the key with the value
            foreach (KeyValuePair<string, string> kv in defineStatements)
            {
                //Console.WriteLine("#define key:_" + kv.Key + "_,value:_" + kv.Value + "_");
                spinachInputString = spinachInputString.Replace(kv.Key, kv.Value);
            }


            // For each "define delay" statement,
            // encapsulate key as parameter of matlab statement
            // for propagation of density operator (append key with "v.")
            foreach (string defDelay in defineDelays)
            {
                spinachInputString = spinachInputString.Replace(defDelay, "[rho,pp]=delay_kope(spinsys, rho,pp, v." + defDelay + "); ");
                //Console.WriteLine("Define delay:_" + defDelay + "_");
            }


            // Capture power level (pl) and channel (ch) from relevant statements
            // Regex is: spaces,"pl",power level (digits),spaces,colon(:),"f",channel(single digit)
            // The power level and channel are then encapsulated as parameters of matlab statement,
            // along with other parameters as follows:
            // First parameter: append power level with "v.pl"
            // Second parmeter: channel number (with preceeding spaces)
            // Third parameter: 90 degree pulse of spectrometer channel
            // Fourth parameter: 90 degree power level of spectrometer channel

            Regex powerRegEx = new Regex(@"\s*pl(\d+)\s*:f(\d)");
            MatchCollection powerMatchList = powerRegEx.Matches(spinachInputString);
            //Console.WriteLine("Count from MatchList: " + powerMatchList.Count);

            foreach (Match aMatch in powerMatchList)
            {
                // Get the number of groups (excluding the first group, which is the full match)
                //Console.WriteLine("No. of groups in current match: " + (aMatch.Groups.Count-1));

                /*DIAGNOSTIC
                for (int i = 0; i < aMatch.Groups.Count; i++)
                {
                    // can choose between escaped or unescaped string
                    //Console.WriteLine("Group Num " + i + ":__" + Regex.Escape(aMatch.Groups[i].Value) + "__");
                    //Console.WriteLine("Group Num " + i + ":__" + Regex.Unescape(aMatch.Groups[i].Value) + "__");                    
                }
                */

                string newString = Regex.Replace(aMatch.Value, @"\s*pl(\d+)\s*:f(\d)", @"pp=set_power_kope(v.pl$1,pp,  $2, " + p90[Convert.ToInt32(aMatch.Groups[1].Value)] + "," + pl90[Convert.ToInt32(aMatch.Groups[2].Value)] + ");");
                //Console.WriteLine("newString:__" + newString + "__");
                spinachInputString = spinachInputString.Replace(aMatch.Value, newString);

            }


            // Resolve gradient statements of the form p23:gp3
            // Capture pulse (p) and gradient pulse (gp) from relevant statements
            // Regex is: spaces,"p",pulse(digits),spaces,colon(:),"gp",gradient pulse(digits)
            // The pulse is then encapsulated as a parameter of matlab statement,
            // for propagation of density operator (append key with "v.").
            // Gradient pulse value not used???
            spinachInputString = Regex.Replace(spinachInputString, @"\s*p(\d+)\s*:gp(\d+)", @" [rho,pp]=delay_kope(spinsys, rho,pp, v.p$1);");


            // Capture composite pulse decoupling (cpd or cpds) and/or channel (ch) from relevant statements
            // The channel is then encapsulated as a parameter of a matlab statement,
            // setting composite pulse decoupling on and offset using the channel.
            // (set cpd_on=1 for decoupling on; cpd_on=0 for decoupling off)
            // Composite pulse decoupling value (cpd) not used???

            // For cpds of form	cpd4:f4 or cpds4:f4 (i.e. the "s" is optional)
            // Regex is: spaces,"cpd" or "cpds",cpd value(digits),spaces,colon(:),"f",channel(digits)
            spinachInputString = Regex.Replace(spinachInputString, @"\s*cpds?(\d+)\s*:f(\d+)", @"pp.cpd_on($2,1)=1; pp.cpd_offset($2,1)=0.0;");

            // For switching decoupling off, in form do:f4
            // Regex is: spaces,"do:f",channel(digits)
            // translated to cpd_on=0 in spinach
            spinachInputString = Regex.Replace(spinachInputString, @"\s*do:f(\d+)", @"pp.cpd_on($1,1)=0;");

            // Resolve explicit delays statements of form 10u, 10m, 10s
            // Capture explicit delays (dl) of various magnitudes (us, ms, s) as well as explicit delay parameters.
            // The delay is multiplied by the appropriate magnitude,
            // then encapsulated as a parameter of matlab statement, for propagation of density operator

            // Regex is: delay(digits),"u" (for microseconds)
            // Delays of 0.1 microseconds or less are not included
            // in spinach calculations (replaced with empty string)
            Regex microSecRegEx = new Regex(@"(\d+)u");
            MatchCollection microSecMatchList = microSecRegEx.Matches(spinachInputString);
            //Console.WriteLine("Count from MatchList: " + microSecMatchList.Count);

            foreach (Match aMatch in microSecMatchList)
            {
                // Get the number of groups (excluding the first group, which is the full match)
                // Console.WriteLine("No. of groups in current match: " + (aMatch.Groups.Count - 1));

                string microSecValue = aMatch.Groups[1].Value;
                //Console.WriteLine("Microsec Value: __" + microSecValue + "__");


                if (Double.Parse(microSecValue) > 0.1)
                {
                    spinachInputString = Regex.Replace(spinachInputString, @"(\d+)u", @" [rho,pp]=delay_kope(spinsys, rho,pp, $1*1e-06); ");
                }
                else
                {
                    spinachInputString = Regex.Replace(spinachInputString, @"(\d+)u", @"");
                }

                /*DIGNOSTICS
                for (int i = 0; i < aMatch.Groups.Count; i++)
                {
                    // can choose between escaped or unescaped string
                    Console.WriteLine("Group Num " + i + ":__" + Regex.Escape(aMatch.Groups[i].Value) + "__");
                    //Console.WriteLine("Group Num " + i + ":__" + Regex.Unescape(aMatch.Groups[i].Value) + "__");
                    
                }
                */
            }


            // Regex is: delay(digits),"m" (for milliseconds)
            spinachInputString = Regex.Replace(spinachInputString, @"(\d+)m", @" [rho,pp]=delay_kope(spinsys, rho,pp, $1*1e-03); ");

            // Regex is: delay(digits),"s" (for seconds)
            spinachInputString = Regex.Replace(spinachInputString, @"(\d+)s", @" [rho,pp]=delay_kope(spinsys, rho,pp, $1*1); ");

            // Regex is: "d",delay parameter(digits)
            // (append key with "v.d" in matlab parameter).
            spinachInputString = Regex.Replace(spinachInputString, @"d(\d+)", @" [rho,pp]=delay_kope(spinsys, rho,pp, v.d$1); ");


            // Pulse Structures
            // Capture the parameters for each type of pulse
            // The parameters are then encapsulated as parameters of matlab statement,
            // for propagation of density operator
            // The parameters included are as follows:
            // First parameter: append pulse (p) with "v.p"
            // Second parameter: append phase (ph) with "v.ph"
            // Third parmeter: channel number (ch)
            // Optional parameters:
            //    multiplier (mult), for multiplication of pulse
            //    shaped pulse (sp), appended with "v.sp", "v.spoff", "v.spnam", as three separate parameters  

            // Format: "   (p1 ph20):f1 "
            // Regex is: "(",spaces,"p",pulse(digits),spaces,"ph",phase(digits),spaces,"):f",channel(single digit)
            spinachInputString = Regex.Replace(spinachInputString, @"\(\s*p(\d+)\s*ph(\d+)\s*\):f(\d)", @"[rho,pp]=pulse_kope(spinsys, rho,pp,v.p$1, v.ph$2,k, $3);");

            // Format: "  (p1*300 ph20):f1 "
            // Regex is: "(",spaces,"p",pulse(digits),asteriks(*),multiplication(digits,dot(.),digits),spaces,"ph",phase(digits),spaces,"):f",channel(single digit)
            spinachInputString = Regex.Replace(spinachInputString, @"\(\s*p(\d+)\*(\d*\.*\d*)\s*ph(\d+)\s*\):f(\d)", @"[rho,pp]=pulse_kope(spinsys, rho,pp,v.p$1*$2, v.ph$3,k, $4);");

            // Format: "  (p1:sp1 ph20:r):f1 "
            // Regex is: "(",spaces,"p",pulse(digits),colon(:),"sp",shaped pulse(digits),spaces,"ph",phase(digits),colon(:),"r",spaces,"):f",channel(single digit)
            // Note: additonal phase correction paramter, "v.phcor", with phase value
            Regex shapedPulseRegEx = new Regex(@"\(\s*p(\d+):sp(\d+)\s*ph(\d+):r\s*\):f(\d)");
            MatchCollection shapedPulseMatchList = shapedPulseRegEx.Matches(spinachInputString);
            //Console.WriteLine("Count from MatchList: " + shapedPulseMatchList.Count);

            foreach (Match aMatch in shapedPulseMatchList)
            {
                // Get the number of groups (excluding the first group, which is the full match)
                //Console.WriteLine("No. of groups in current match: " + (aMatch.Groups.Count-1));

                /*DIAGNOSTIC    
                for (int i = 0; i < aMatch.Groups.Count; i++)
                {
                    // can choose between escaped or unescaped string
                    Console.WriteLine("Group Num " + i + ":__" + Regex.Escape(aMatch.Groups[i].Value) + "__");
                    //Console.WriteLine("Group Num " + i + ":__" + Regex.Unescape(aMatch.Groups[i].Value) + "__");
                    
                }
                */

                string newString = Regex.Replace(aMatch.Value, @"\(\s*p(\d+):sp(\d+)\s*ph(\d+):r\s*\):f(\d)", @"[rho,pp]=pulse_shaped_kope(spinsys, rho,pp,v.p$1, v.sp$2,v.spoff$2, v.spnam$2, v.ph$3+ v.phcor$3,k, $4," + p90[Convert.ToInt32(aMatch.Groups[4].Value)] + "," + pl90[Convert.ToInt32(aMatch.Groups[4].Value)] + ", compress_shape);");
                //Console.WriteLine("newString:__" + newString + "__");
                spinachInputString = spinachInputString.Replace(aMatch.Value, newString);

            }

            // Format: "   p1 ph20 "
            // Regex is: spaces,"p",pulse(digits),spaces,"ph",phase(digits),spaces
            // No explicit channel specified, so "channXXX" used for final parameter
            spinachInputString = Regex.Replace(spinachInputString, @"\s*p(\d+)\s*ph(\d+)\s*", @"[rho,pp]=pulse_kope(spinsys, rho,pp,v.p$1, v.ph$2,k, channXXX);");

            // Receiver
            // Format: "   go=2 ph31 "
            // Regex is: "go",spaces,equals(=),spaces,pulse(digits),spaces,"ph",phase(digits)
            // Call receiver function with phase (ph) and pulse (p) paramters
            spinachInputString = Regex.Replace(spinachInputString, @"go\s*=\s*(\d+)\s*ph(\d+)", @"[rho,pp]=receiver_kope(spinsys, rho,pp, v.ph$2,k, $1);");



            // Read input from experiment macro
            // TEMP: USE MODIFIED MACRO, WHERE VALUES ARE TEMP HARDCODED
            //string macroInputString = File.ReadAllText(@"..\..\trosy-st2pt_modified.txt");


            //Console.Write(macroInputString);

            // USING NMRPLUS GENERATED SCRIPT:
            // Isolate the acquisition parameters as everything between the 3rd set
            // of triplet of double quotes (""").
            // Since in a string literal, a double quote is represented by two double quotes,
            // a triplet of double quotes is represented by six double quotes in a row ("""""")
            //macroInputString = Regex.Replace(macroInputString, @"^.*"""""", """"""(.*)""""""", "$1");
            Match myMatch = Regex.Match(macroInputString, @"self\.fileList\.append\(([^\)]*)\)");
            macroInputString = myMatch.Groups[1].Captures[0].Value + "\n"; ;
            

            // CARA VERSION
            // Remove both lua comments and blank lines from the macro script
            // (each occurrence is replaced with an empty string)
            // Regex for lua comment: --(--),anything(.*),newline(\n)
            // Regex for blank line: startline(^),spaces(\s*),endline($),newline(\n)
            // Regex used in multiline mode, to allow start and end of line detection
            //macroInputString = Regex.Replace(macroInputString, @"(^\s*\-\-.*\n|^\s*$\n|\}\r\n)", "", RegexOptions.Multiline);            
            // NMRplus VERSION
            // Remove C# comments and blank lines from the macro script
            // (each occurrence is replaced with an empty string)
            // Regex for NMRplus comment: hash(#),anything(.*),newline(\n)
            // Regex for blank line: startline(^),spaces(\s*),endline($),newline(\n)
            // Regex used in multiline mode, to allow start and end of line detection
//            macroInputString = Regex.Replace(macroInputString, @"(^\s*\#.*\n|^\s*$\n|\}\r\n)", "", RegexOptions.Multiline);

            // Remove lua lines with any of the following formats
            // (each occurrence is replaced with an empty string):
            // script12, pulprog, xcpr, exp_environment, "1 something""
            // Regex used in multiline mode, to allow start and end of line detection
            // FOR NMRPLUS: replace with final regex below, to get rid of remaining lines not containing "v."
            //macroInputString = Regex.Replace(macroInputString, @"^.*(script\d+|pulprog|xcpr|exp_environment|""\d\s+\w+\s*"").*$\n", "", RegexOptions.Multiline);
            macroInputString = Regex.Replace(macroInputString, @"^.*(""""""|pulprog).*$\n", "", RegexOptions.Multiline);
            macroInputString = Regex.Replace(macroInputString, @"^\d+\s+.*$\n", "", RegexOptions.Multiline);

            // Modify any parameter containing "nam", i.e. both ["spnam1"] and gpnam10 formats
            // to become, e.g.  v.gpnam10='wave/SINE.100';
            // Regex: startline(^),spaces or open square bracket or double quotes ([\s*\[""]),key(any word containing "nam"),anything(.*),
            //        equals(=),spaces(\s*),double quotes(""),value(.*),double quotes(""),anything(.*),endline($),newline(\n) 
            //macroInputString = Regex.Replace(macroInputString, @"^[\s*\[""]*(\w*nam\w*).*=\s*""(.*)"".*$\n", "v.$1=\'wave/$2\';\n", RegexOptions.Multiline);
            // FOR NMRplus
            //  Note: "components." is only a prefix of spnam values, so is optional
            //  Formats:
            //    ExpScope.BrukerFileCommand("spnam1", "components.sinc1_0") or 
            //    ExpScope.BrukerCommand("gpnam1", "SINE.100") 
//            macroInputString = Regex.Replace(macroInputString, @"^\s*ExpScope\.Bruker.*Command\(""(\w*nam\w*)"",\s*""(components\.)?(.*)""\s*\).*$\n", "v.$1=\'wave/$3\';\n", RegexOptions.Multiline);
            macroInputString = Regex.Replace(macroInputString, @"^(\w*nam\w*)\s+(\S*).*$\n", "v.$1=\'wave/$2\';\n", RegexOptions.Multiline);

            // FOR NNMRplus: extra Regex for "cpdprg"
            // Format: ExpScope.BrukerFileCommand("cpdprg4", "components.waltz16_31")
            // Change to: v.cpdprg4=waltz16_31
//            macroInputString = Regex.Replace(macroInputString, @"^\s*ExpScope\.Bruker.*Command\(""(cpdprg.*)"",\s*""components\.(.*)""\s*\).*$\n", "v.$1=$2;\n", RegexOptions.Multiline);

            // pulse length conversion or pcpd
            // pulse length specified in microseconds
            // so value is appended with "*1e-6"
            // can cope with the following formats:
            //   p12 = 345
            //   p12 = 123*345
            //   p12 = 345.678
            //   p12 = 123*345.678
            //macroInputString = Regex.Replace(macroInputString, @"^\s*(p\d+|pcpd\d+)\s*=\s*(\d*\*?\d*\.?\d*)\s*.*$\n", "v.$1=$2*1e-6;\n", RegexOptions.Multiline);
            // FOR NNMRplus
            // Regex: startline(^),spaces(\s*),"ExpScope.Bruker",anything(.*, e.g. "File"),"Command",open bracket(\(),double quotes(""),
            //        p or pcpd,digits,double quotes(""),comma(,),spaces(\s*),value(\d*\*?\d*\.?\d*),spaces(\s*),anything(.*),endline($),newline(\n) 
//            macroInputString = Regex.Replace(macroInputString, @"^\s*ExpScope\.Bruker.*Command\(""(p\d+|pcpd\d+)"",\s*(\d*\*?\d*\.?\d*)\s*.*$\n", "v.$1=$2*1e-6;\n", RegexOptions.Multiline);
            macroInputString = Regex.Replace(macroInputString, @"^(p\d+|pcpd\d+)\s+(\d*\*?\d*\.?\d*)\s*$\n", "v.$1=$2*1e-6;\n", RegexOptions.Multiline);

            // outfile:write( "\nv."..key.."="..value..";" )
            // ["whatever"] = "something in quotes"
            //macroInputString = Regex.Replace(macroInputString, @"^\s*\[""(.*)""\]\s*=\s*""(.*)"".*$\n", "v.$1=$2;\n", RegexOptions.Multiline);
            // use key,spaces,value format
            macroInputString = Regex.Replace(macroInputString, @"^([^v\.]\S*)\s*(\S*).*$\n", "v.$1=$2;\n", RegexOptions.Multiline);

            // ["in0"] = digits
            //macroInputString = Regex.Replace(macroInputString, @"^\s*\[""(.*)""\]\s*=\s*(\d*\.?\d*)\s*.*$\n", "v.$1=$2;\n", RegexOptions.Multiline);

            // CARA VERSION
            // outfile:write( "\nv."..key.."="..value..";" )
            // remove the last comma(,) and everything after comma (+ whitespace)
            //macroInputString = Regex.Replace(macroInputString, @"^\s*(\w*)\s*=\s*([\-\w\.\(\)\*/]*)\,.*$\n", "v.$1=$2;\n", RegexOptions.Multiline);
            // FOR NMRPLUS
            // Use \S* (capital S) to specify that parameter is anything other than whitespace (this removes processing parameters, such as "1 FnMode")
            // Regex: startline(^),spaces(\s*),"ExpScope.Bruker",anything(.*, e.g. "File"),"Command",open bracket(\(),double quotes(""),
            //        not whitespace(\S*),double quotes(""),comma(,),spaces(\s*),value(.*),spaces(\s*),close bracket(\)),anything(.*),endline($),newline(\n) 
//            macroInputString = Regex.Replace(macroInputString, @"^\s*ExpScope\.BrukerCommand\(""(\S*)"",\s*(.*)\s*\).*$\n", "v.$1=$2;\n", RegexOptions.Multiline);

            // FOR NMRPLUS: remove any remaining lines that do not start with "v"
            // (each occurrence is replaced with an empty string):
            // Regex used in multiline mode, to allow start and end of line detection
//            macroInputString = Regex.Replace(macroInputString, @"^[a-u|w-z|A-U|W-Z|\d|\s].*$\n", "", RegexOptions.Multiline);

            // Write output formatted for spinach to file
            //System.IO.FileStream fs2 = new FileStream(@"..\..\trosy-st2pt_to_spinach.txt", System.IO.FileMode.Truncate);
            //byte[] outputData = System.Text.Encoding.ASCII.GetBytes(macroInputString);
            string outputString = macroInputString;

            //Console.Write(macroInputString);
            //fs2.Write(outputData, 0, outputData.Length)


            // Add phase table title as matlab comment
            //outputData = System.Text.Encoding.ASCII.GetBytes("%% Phase Table\n");
            //fs2.Write(outputData, 0, outputData.Length);
            outputString = outputString + "%% Phase Table\n";

            // Add key/value pairs of phase+calculus array to output
            foreach (KeyValuePair<string, string> kv in phaseCalculusStatements)
            {
                //outputData = System.Text.Encoding.ASCII.GetBytes("v." + kv.Key + "=" + kv.Value + ";\n");
                //fs2.Write(outputData, 0, outputData.Length);
                outputString = outputString + "v." + kv.Key + "=" + kv.Value + ";\n";
            }


            // For each line in the formatted array of lines,
            // first write out the original (unformatted) line from bruker pulse program, prefixed with "%% --".
            // Then, write out the line formatted for Spinach input,
            // prefixing each with "events{1,ek}='" and suffixing with "'; ek=ek+1;\n".
            // "ek" represents an "events counter", and is incremented after each event call
            List<string> brukerStringArray = new List<string>();
            using (StringReader reader = new StringReader(brukerInputString))
            {
                string line;
                while ((line = reader.ReadLine()) != null)
                {
                    brukerStringArray.Add("%% --" + line + "\n");
                    //Console.Write("events{1,ek}='" + line + "'; ek=ek+1;\n");
                }
            }

            List<string> spinachStringArray = new List<string>();
            using (StringReader reader = new StringReader(spinachInputString))
            {
                string line;
                while ((line = reader.ReadLine()) != null)
                {
                    spinachStringArray.Add("events{1,ek}='" + line + "'; ek=ek+1;\n");
                    //Console.Write("events{1,ek}='" + line + "'; ek=ek+1;\n");
                }
            }


            for (int i = 0; i < brukerStringArray.Count; i++)
            {
                //outputData = System.Text.Encoding.ASCII.GetBytes(brukerStringArray[i] + spinachStringArray[i]);
                //fs2.Write(outputData, 0, outputData.Length);
                outputString = outputString + brukerStringArray[i] + spinachStringArray[i];
            }

            //Console.Write("BrukerStringCount: " + brukerStringArray.Count);
            //Console.Write("SpinachStringCount: " + spinachStringArray.Count); 

            // close the to_spinach file
            //fs2.Close();

            return outputString;

        } // end TranslateToSpinach

        //public static double calc_power_set(double curren_pulse90, double reference_shape_constant, double calibration_pulse90, double calibration_power_db)
        //{
            //double reference_rect_pulse90 = 10.0; //--mks --from NMRSIM
            //double reference_rect_power_rf=50000.0; //--Hz       NMRSIM
            //double calibration_constant_db = 0.0;
            //double out_put = 120;

            //out_put = 20.0 * Math.Log10(reference_shape_constant * curren_pulse90 / (calibration_pulse90)) + calibration_power_db;
            //return out_put;
        //} // end calc_power_set

    } // end SpinachSetup class

} // end IdeaNMR.Shared namespace
